package org.dmikis.jmmengine.models;

import javax.swing.*;
import java.awt.GridLayout;

import org.dmikis.jmmengine.models.EquationSolver.EquationUnsolvedException;

import static java.lang.Math.PI;
import static java.lang.Math.sin;
import static java.lang.Integer.parseInt;
import static java.lang.Double.parseDouble;

/**
 * @author dmikis
 * @version 0.3
 */
public class TaskOneModel implements Model {
    
    /**
     * Create new model instance with specified output field and parameters set
     */
    public TaskOneModel() {
	this.field = new Scalar2DField(DEFAULT_HORIZONTAL_DIMENSION, DEFAULT_VERTICAL_DIMENSION);
    }
    
    /**
     * Compute result according to given model
     */
    @Override
    public void compute() {
	double deltaX = 1.0 / this.field.getSize().width;
	double deltaT = this.parameterTimeLimit / this.field.getSize().height;
	
	this.status = 0;
	
	try {
	    // Initialize field by boarder conditions
	    for (int x = 0; x < this.field.getSize().width; x++) {
		this.field.setPointValue(x, 0, -1*sin(PI * x * deltaX));
	    }
	    for (int y = 0; y < this.field.getSize().height; y++) {
		this.field.setPointValue(0, y, 0);
	    }
	    // Compute
	    for (int x = 1; x < this.field.getSize().width; x++) {
		this.status = (x * 100) / (this.field.getSize().width); 
		for (int y=1; y < this.field.getSize().height; y++) {
		    DifferentiableFunction f = new TaskOneFunction(
			    deltaT, deltaX,
			    this.field.getPointValue(x-1, y-1),
			    this.field.getPointValue(x-1, y),
			    this.field.getPointValue(x, y-1));
		    EquationSolver eq = new EquationSolver(f, this.parameterAccuracy);
		    this.field.setPointValue(x, y, eq.getResult());
		}
	    }
	}
	catch (EquationUnsolvedException e) {
	    System.err.println(e.getType());
	}
	finally {
	    this.field.renderFieldVisualization();
	}
    }
    
    /**
     * Get status of computational process as percentage.
     * 
     * @return percentage of completed process
     */
    @Override
    public int getStatus() {
	return this.status;
    }
    
    /**
     * Generate and return panel for parameters dialog.
     * After changing parameters viewer have to execute <code>renewModelParameters()</code> method.
     * 
     * @return panel contains label and input fields for every parameter required by model
     */
    @Override
    public JPanel getParametersPanel() {
	return this.parametersPanel;
    }
    
    /**
     * Renew model parameters by values obtained from <code>ModelParametersPanel</code>
     * class instance returned by <code>getParametersPanel()</code> method.
     * 
     * <b>Warning!</b> After renew model parameters you have to renew all links to the result image!
     * 
     * @throws IllegalArgumentException if one or more parameters parsed as non-positive value.
     */
    @Override
    public void renewModelParameters() throws IllegalArgumentException {
	int tempParamHorDim, tempParamVerDim;
	double tempParamTime, tempParamAccuracy;
	tempParamHorDim   = parseInt(this.parametersPanel.fieldHorizontalDimension.getText());
	tempParamVerDim   = parseInt(this.parametersPanel.fieldVerticalDimension.getText());
	tempParamTime     = parseDouble(this.parametersPanel.fieldTimeLimit.getText());
	tempParamAccuracy = parseDouble(this.parametersPanel.fieldAccuracy.getText());
	if (tempParamTime <= 0.0 || tempParamAccuracy <= 0.0) {
	    throw new IllegalArgumentException("Parameters must be positive number!");
	}
	this.field = new Scalar2DField(tempParamHorDim, tempParamVerDim);
	this.parameterTimeLimit = tempParamTime;
	this.parameterAccuracy = tempParamAccuracy;
    }
    
    /**
     * Get image representation of result.
     * 
     * @return result image
     */
    public Result getResult() {
	return this.field;
    }
    
    /**
     * @author dmikis
     * @version 0.1
     * 
     * Class implements view of model's parameters.
     */
    @SuppressWarnings("serial")
    public class ModelParametersPanel extends JPanel {
	
	/**
	 * Create new instance of parameters panel and fill it by inputs.
	 */
	public ModelParametersPanel() {
	    super(new GridLayout(4, 2));
	    add(new JLabel("Horizontal dimension:"));
	    add(fieldHorizontalDimension);
	    add(new JLabel("Vertical dimension;"));
	    add(fieldVerticalDimension);
	    add(new JLabel("Time limit:"));
	    add(fieldTimeLimit);
	    add(new JLabel("Accuracy:"));
	    add(fieldAccuracy);
	}
	
	// X dimension of model's result numeric field
	protected JTextField fieldHorizontalDimension = new JTextField(Integer.toString(DEFAULT_HORIZONTAL_DIMENSION));
	// Y dimension of model's result numeric field
	protected JTextField fieldVerticalDimension = new JTextField(Integer.toString(DEFAULT_HORIZONTAL_DIMENSION));
	// Time interval to calculate by model
	protected JTextField fieldTimeLimit = new JTextField(Double.toString(DEFAULT_TIME_LIMIT));
	// Computation accuracy
	protected JTextField fieldAccuracy = new JTextField(Double.toString(DEFAULT_ACCURACY));
    }
    
    private static final int DEFAULT_HORIZONTAL_DIMENSION = 250;
    private static final int DEFAULT_VERTICAL_DIMENSION = 250;
    private static final double DEFAULT_TIME_LIMIT = 10.0;
    private static final double DEFAULT_ACCURACY = 0.01;
    
    private volatile int status;
    
    protected ModelParametersPanel parametersPanel = new ModelParametersPanel();
    
    protected Scalar2DField field;
    
    protected double parameterTimeLimit = DEFAULT_TIME_LIMIT;
    protected double parameterAccuracy = DEFAULT_ACCURACY;
}
